﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.InteropServices;

namespace Assimp.Unmanaged {
    internal sealed class UnmanagedWin32LibraryImplementation : UnmanagedLibraryImplementation {
        public override String DllExtension {
            get {
                return ".dll";
            }
        }

        public UnmanagedWin32LibraryImplementation(String defaultLibName, Type[] unmanagedFunctionDelegateTypes)
            : base(defaultLibName, unmanagedFunctionDelegateTypes) {
        }

        protected override IntPtr NativeLoadLibrary(String path) {
            IntPtr libraryHandle = WinNativeLoadLibrary(path);

            if (libraryHandle == IntPtr.Zero && ThrowOnLoadFailure) {
                Exception innerException = null;

                //Keep the try-catch in case we're running on Mono. We're providing our own implementation of "Marshal.GetHRForLastWin32Error" which is NOT implemented
                //in mono, but let's just be cautious.
                try {
                    int hr = GetHRForLastWin32Error();
                    innerException = Marshal.GetExceptionForHR(hr);
                }
                catch (Exception) { }

                if (innerException != null)
                    throw new AssimpException(String.Format("Error loading unmanaged library from path: {0}\n\n{1}", path, innerException.Message), innerException);
                else
                    throw new AssimpException(String.Format("Error loading unmanaged library from path: {0}", path));
            }

            return libraryHandle;
        }

        protected override IntPtr NativeGetProcAddress(IntPtr handle, String functionName) {
            return GetProcAddress(handle, functionName);
        }

        protected override void NativeFreeLibrary(IntPtr handle) {
            FreeLibrary(handle);
        }

        private int GetHRForLastWin32Error() {
            //Mono, for some reason, throws in Marshal.GetHRForLastWin32Error(), but it should implement GetLastWin32Error, which is recommended than
            //p/invoking it ourselves when SetLastError is set in DllImport
            int dwLastError = Marshal.GetLastWin32Error();

            if ((dwLastError & 0x80000000) == 0x80000000)
                return dwLastError;
            else
                return (dwLastError & 0x0000FFFF) | unchecked((int)0x80070000);
        }

        #region Native Methods

        [DllImport("kernel32.dll", CharSet = CharSet.Ansi, BestFitMapping = false, SetLastError = true, EntryPoint = "LoadLibrary")]
        private static extern IntPtr WinNativeLoadLibrary(String fileName);

        [DllImport("kernel32.dll", SetLastError = true)]
        private static extern bool FreeLibrary(IntPtr hModule);

        [DllImport("kernel32.dll")]
        private static extern IntPtr GetProcAddress(IntPtr hModule, String procName);

        #endregion
    }
}
